package com.atguigu.gulimall.search.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.aggregations.StringTermsBucket;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Highlight;
import co.elastic.clients.elasticsearch.core.search.HighlightField;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.json.JsonData;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.to.es.SkuEsModel;
import com.atguigu.common.utils.R;
import com.atguigu.gulimall.search.constant.ElasticConstant;
import com.atguigu.gulimall.search.feign.ProductServiceFeign;
import com.atguigu.gulimall.search.service.MallSearchService;
import com.atguigu.gulimall.search.vo.AttrResponseVo;
import com.atguigu.gulimall.search.vo.SearchParam;
import com.atguigu.gulimall.search.vo.EsSearchResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author: jianPing
 * @date: 2022/3/14 16:54
 * @description:
 */
@Service
@Slf4j
public class MallSearchServiceImpl implements MallSearchService {
    @Autowired
    ElasticsearchClient client;
    @Autowired
    ProductServiceFeign productServiceFeign;


    @Override
    public EsSearchResponse search(SearchParam param) {
        //1、动态构建出DSL查询语句
        SearchRequest searchRequest = buildDSL(param);
        //2、执行检索请求
        SearchResponse<SkuEsModel> response = null;
        try {
            response = client.search(searchRequest, SkuEsModel.class);
        } catch (Exception e) {
            log.warn("MallSearchServiceImpl查询es数据失败！{}：", e);
        }

        //3、分析响应数据封装成需要的结果
        return parseSearchResponse(param, response);
    }

    /**
     * 根据传入的参数构建DSL查询条件
     */
    private SearchRequest buildDSL(SearchParam param) {
        List<Query> queryMusts = new ArrayList<>();
        List<Query> queryFilters = new ArrayList<>();
        //构建查询请求
        SearchRequest.Builder searchBuild = new SearchRequest.Builder()
                .index(ElasticConstant.productIndex)
                .from(param.getPageNum() - 1)
                .size(ElasticConstant.pageSize);
        //关键字查询
        if (StringUtils.isNotBlank(param.getKeyword())) {
            //查询结果高亮
            HighlightField highlightField = new HighlightField.Builder().preTags("<b style='color:red'>").postTags("</b").build();
            searchBuild.highlight(h -> h.fields("skuTitle", highlightField));
            Query query = new Query.Builder().match(m -> m.field("skuTitle").query(q -> q.stringValue(param.getKeyword()))).build();
            queryMusts.add(query);
        }
        // 3级分类查询
        if (param.getCatalog3Id() != null) {
            Query query = new Query.Builder().term(e -> e.field("catelogId").value(v -> v.longValue(param.getCatalog3Id()))).build();
            queryFilters.add(query);
        }
        // 品牌查询
        if (param.getBrandId() != null && param.getBrandId().size() > 0) {
            List<FieldValue> collect = param.getBrandId().stream().map(id -> new FieldValue.Builder().longValue(id).build()).collect(Collectors.toList());
            Query query = new Query.Builder().terms(e -> e.field("brandId").terms(v -> v.value(collect))).build();
            queryFilters.add(query);
        }
        //库存查询
        if (param.getHasStock() != null) {
            Query query = new Query.Builder().term(e -> e.field("hasStock").value(v -> v.booleanValue(param.getHasStock() == 1))).build();
            queryFilters.add(query);
        }
        //价格区间查询
        if (StringUtils.isNotBlank(param.getSkuPrice())) {
            Query query;
            if (param.getSkuPrice().startsWith("_")) {
                long to = Long.parseLong(param.getSkuPrice().split("_")[1]);
                query = new Query.Builder().range(e -> e.field("skuPrice").gte(JsonData.of(0)).lte(JsonData.of(to))).build();
            } else if (param.getSkuPrice().endsWith("_")) {
                long from = Long.parseLong(param.getSkuPrice().split("_")[0]);
                query = new Query.Builder().range(e -> e.field("skuPrice").gte(JsonData.of(from))).build();
            } else {
                String[] s = param.getSkuPrice().split("_");
                long from = Long.parseLong(s[0]);
                long to = Long.parseLong(s[1]);
                query = new Query.Builder().range(e -> e.field("skuPrice").gte(JsonData.of(from)).lte(JsonData.of(to))).build();
            }
            queryFilters.add(query);
        }
        //属性查询
        if (param.getAttrs() != null && param.getAttrs().size() > 0) {
            for (String attr : param.getAttrs()) {
                String[] str = attr.split("_", 2);
                long attrId = Long.parseLong(str[0]);
                List<String> attrValues = Arrays.asList(str[1].split(":"));
                List<FieldValue> fieldValues = attrValues.stream().map(s -> new FieldValue.Builder().stringValue(s).build()).collect(Collectors.toList());
                Query queryAttrId = new Query.Builder().term(e -> e.field("attrs.attrId").value(v -> v.longValue(attrId))).build();
                Query queryAttrValues = new Query.Builder().terms(e -> e.field("attrs.attrValue").terms(t -> t.value(fieldValues))).build();
                Query queryAttr = new Query.Builder().nested(n -> n.path("attrs").query(q -> q.bool(b -> b.must(queryAttrId, queryAttrValues)))).build();
                queryFilters.add(queryAttr);
            }
        }
        //排序
        if (StringUtils.isNotBlank(param.getSort())) {
            String[] sortStr = param.getSort().split("_", 2);
            String sortField = sortStr[0];
            String orderStr = sortStr[1];
            if ("asc".equals(orderStr)) {
                searchBuild.sort(s -> s.field(f -> f.field(sortField).order(SortOrder.Asc)));
            } else
                searchBuild.sort(s -> s.field(f -> f.field(sortField).order(SortOrder.Desc)));
        }
        //添加查询条件
        SearchRequest searchRequest = searchBuild
                .query(q -> q
                        .bool(b -> b
                                .must(queryMusts)
                                .filter(queryFilters)
                        ))
                //品牌聚合
                .aggregations("brand_agg", agg -> agg
                        .terms(t -> t
                                .field("brandId")
                                .size(ElasticConstant.totalBrandDisplay))
                        .aggregations("brand_name_agg", nameAgg -> nameAgg
                                .terms(t -> t
                                        .field("brandName")
                                        .size(ElasticConstant.totalBrandDisplay)))
                        .aggregations("brand_img_agg", imgAgg -> imgAgg.terms(t -> t
                                .field("brandImg")
                                .size(ElasticConstant.totalBrandDisplay)))
                )
                //分类聚合
                .aggregations("category_agg", categoryAgg -> categoryAgg
                        .terms(e -> e
                                .field("catelogId")
                                .size(ElasticConstant.totalCategoryDisplay))
                        .aggregations("category_name_agg", e -> e
                                .terms(t -> t
                                        .field("catelogName")
                                        .size(ElasticConstant.totalCategoryDisplay))))
                //属性聚合
                .aggregations("attr_agg", attrAgg -> attrAgg
                        .nested(nested -> nested
                                .path("attrs"))
                        .aggregations("attr_id_agg", idAgg -> idAgg
                                .terms(t -> t
                                        .field("attrs.attrId")
                                        .size(ElasticConstant.totalAttrDisplay))
                                .aggregations("attr_name_agg", nameAgg -> nameAgg.terms(t -> t.field("attrs.attrName").size(ElasticConstant.totalAttrDisplay)))
                                .aggregations("attr_value_agg", nameAgg -> nameAgg.terms(t -> t.field("attrs.attrValue").size(ElasticConstant.totalAttrDisplay)))
                        )
                ).build();
        return searchRequest;
    }

    /**
     * 封装es查询结果
     */
    private EsSearchResponse parseSearchResponse(SearchParam param, SearchResponse<SkuEsModel> response) {
        if (response == null) {
            return null;
        }
        EsSearchResponse esSearchResponse = new EsSearchResponse();
        //封装页码相关信息
        esSearchResponse.setPageNum(param.getPageNum());
        if (response.hits().total() != null) {
            long total = response.hits().total().value();
            int totalPages = (int) (total / 10 + (total % 10 != 0 ? 1 : 0));
            esSearchResponse.setTotal(total);
            esSearchResponse.setTotalPages(totalPages);
            List<Integer> pageNavs = new ArrayList<>();
            for (int i = 1; i <= total; i++) {
                pageNavs.add(i);
            }
            esSearchResponse.setPageNavs(pageNavs);
        }
        //封装查询到的文档信息
        List<Hit<SkuEsModel>> hits = response.hits().hits();
        if (hits != null && hits.size() > 0) {
            List<SkuEsModel> products = hits.stream()
                    .map(item -> {
                        SkuEsModel skuEsModel = item.source();
                        if (skuEsModel != null && item.highlight() != null && item.highlight().size() > 0) {
                            List<String> highlight = item.highlight().get("skuTitle");
                            skuEsModel.setSkuTitle(highlight.get(0));
                        }
                        return skuEsModel;
                    })
                    .collect(Collectors.toList());
            esSearchResponse.setProducts(products);
        }
        //封装查询结果涉及到的信息
        Map<String, Aggregate> aggregations = response.aggregations();
        if (aggregations != null && aggregations.size() > 0) {
            //属性聚合
            Aggregate attr_agg = aggregations.get("attr_agg");
            if (attr_agg != null) {
                List<EsSearchResponse.AttrVo> collect = attr_agg
                        .nested()
                        .aggregations()
                        .get("attr_id_agg")
                        .lterms()
                        .buckets()
                        .array()
                        .stream()
                        .map(item -> {
                            EsSearchResponse.AttrVo attrVo = new EsSearchResponse.AttrVo();
                            Long attrId = item.key();
                            String attr_name = item
                                    .aggregations()
                                    .get("attr_name_agg")
                                    .sterms()
                                    .buckets()
                                    .array()
                                    .get(0)
                                    .key();
                            List<String> attr_values = item
                                    .aggregations()
                                    .get("attr_value_agg")
                                    .sterms()
                                    .buckets()
                                    .array()
                                    .stream()
                                    .map(StringTermsBucket::key)
                                    .collect(Collectors.toList());
                            attrVo.setAttrId(attrId);
                            attrVo.setAttrName(attr_name);
                            attrVo.setAttrValue(attr_values);
                            return attrVo;
                        }).collect(Collectors.toList());
                esSearchResponse.setAttrs(collect);
            }
            //分类聚合
            Aggregate category_agg = aggregations.get("category_agg");
            if (category_agg != null) {
                List<EsSearchResponse.CatelogVo> collect = category_agg
                        .lterms()
                        .buckets()
                        .array()
                        .stream()
                        .map(item -> {
                            EsSearchResponse.CatelogVo catelogVo = new EsSearchResponse.CatelogVo();
                            long categoryId = item.key();
                            String categoryName = item
                                    .aggregations()
                                    .get("category_name_agg")
                                    .sterms()
                                    .buckets()
                                    .array()
                                    .get(0)
                                    .key();
                            catelogVo.setCatelogId(categoryId);
                            catelogVo.setCatelogName(categoryName);
                            return catelogVo;
                        }).collect(Collectors.toList());
                esSearchResponse.setCatelogs(collect);
            }
            //品牌聚合
            Aggregate brand_agg = aggregations.get("brand_agg");
            if (brand_agg != null) {
                List<EsSearchResponse.BrandVo> collect = brand_agg.lterms().buckets().array()
                        .stream()
                        .map(item -> {
                            EsSearchResponse.BrandVo brandVo = new EsSearchResponse.BrandVo();
                            long brandId = item.key();
                            String brandImg = item.aggregations()
                                    .get("brand_img_agg")
                                    .sterms()
                                    .buckets()
                                    .array()
                                    .get(0)
                                    .key();
                            String brandName = item.aggregations()
                                    .get("brand_name_agg")
                                    .sterms()
                                    .buckets()
                                    .array()
                                    .get(0)
                                    .key();
                            brandVo.setBrandId(brandId);
                            brandVo.setBrandImg(brandImg);
                            brandVo.setBrandName(brandName);
                            return brandVo;
                        }).collect(Collectors.toList());
                esSearchResponse.setBrands(collect);
            }
        }
        //添加面包屑导航功能
        if (param.getAttrs() != null && param.getAttrs().size() > 0) {
            List<EsSearchResponse.NavVo> collect = param.getAttrs().stream()
                    .map(attr -> {
                        EsSearchResponse.NavVo navVo = new EsSearchResponse.NavVo();
                        String[] s = attr.split("_");
                        R r = productServiceFeign.getAttrInfo(Long.parseLong(s[0]));
                        if (r.isSuccess()) {
                            AttrResponseVo data = r.getData(new TypeReference<AttrResponseVo>() {
                            });
                            navVo.setNavName(data.getAttrName());
                        } else {
                            navVo.setNavName(s[0]);
                        }
                        String encode = null;
                        try {
                            encode = URLEncoder.encode(attr, "utf-8");
                            encode = encode.replace("+","%20");
                        } catch (UnsupportedEncodingException e) {
                            e.printStackTrace();
                        }
                        String replace = param.get_queryString().replace("&attrs=" + encode, "");
                        navVo.setLink("http://search.gulimall.com/list.html?" + replace);
                        navVo.setNavValue(s[1]);
                        return navVo;
                    })
                    .collect(Collectors.toList());
            esSearchResponse.setNavs(collect);
        }

        return esSearchResponse;
    }
}
